<?php

/**
 * YFileUsageBehavior 文件使用上传行为
 *
 * @author Hua Yang <htmlcook@gmail.com>
 * @package common.components
 */
class YFileUsageBehavior extends CActiveRecordBehavior
{
	/**
	 * 对象文件字段
	 *
	 * @var array
	 */
	public $fields = array();

	/**
	 * 字段对应的文件模型列表 一个字段对应多个文件
	 *
	 * @var array
	 */
	private $_files = array();

	private $_oldFileIds = array();

	/**
	 * 获取图片
	 *
	 * @param string $name
	 * @param array $htmlOptions
	 * @return string
	 */
	public function getImage($name = File::IMAGE_POST_THUMBNAIL, $htmlOptions = array())
	{
		if ($imageFile = $this->getImageFile()) {
			return $imageFile->getImage($name, $htmlOptions);
		}
	}

	/**
	 * 获取图片文件 根据模型中的image属性
	 *
	 * @return File|null
	 */
	public function getImageFile()
	{
		if ($fileId = current($this->getFileIdsByField('image'))) {
			return File::findFromCache($fileId);
		}
	}

	/**
	 * 根据对象字段获取该字段下的文件ids
	 *
	 * @param string $field 对象字段
	 * @param boolean $real 真实的已保存的
	 * @throws CException
	 * @return array
	 */
	public function getFileIdsByField($field, $real = false)
	{
		$fields = $this->getUploadFields();
		if (!isset($fields[$field])) {
			throw new CException($field . '字段不是文件字段');
		}

		$owner = $this->getOwner();

		if (isset($owner->$field) && !$real) {
			return $this->parseIds($owner->$field);
		}

		if ($owner->getIsNewRecord()) {
			return array();
		}

		if ($fields[$field]['isSelf']) {
			return $this->_oldFileIds[$field];
		}

		$cacheKey = $this->getfileIdsCacheKey($owner->id, $field);
		if (($fieldIds = Yii::app()->getCache()->get($cacheKey)) === false) {
			$connection = $this->getOwner()->getDBConnection();
			$command = $connection->createCommand()
				->select('file_id')
				->from('{{file_usage}}')
				->where('object_id=:object_id AND bundle=:bundle AND field=:field', array(
					':object_id' => $owner->getPrimaryKey(),
					':bundle' => get_class($owner),
					':field' => $field));
			if ($fields[$field]['sort']) {
				$command->order('weight');
			}
			if ($fields[$field]['many'] !== true) {
				$command->limit($fields[$field]['many']);
			}
			$fieldIds = $command->queryColumn();
			Yii::app()->getCache()->add($cacheKey, $fieldIds, Yii::app()->option->get('_file_usage_ids_expire', 2592000)); //30天
		}

		return $fieldIds;
	}

	/**
	 * 获取上传文件字段列表（已预处理过）
	 *
	 * @return
	 */
	public function getUploadFields()
	{
		static $cache = array();
		$owner = $this->getOwner();

		$className = get_class($owner);
		if (!isset($cache[$className])) {
			$maxSize = File::fileMaxSize();
			$rule = array(
				'types' => File::fileTypes(),
				'maxSize' => $maxSize,
				'minSize' => 1024,
				'tooLarge' => sprintf('{file}太大了，必须小于%s', Yii::app()->format->size($maxSize)),
				'tooSmall' => sprintf('{file}太小了，必须大于%s', Yii::app()->format->size(1024)),
			);

			$fields = array();

			foreach ((array)$this->fields as $field => $params) {
				$params = (array)$params;

				if (empty($params['many'])) {
					$params['many'] = 1;
				} elseif (is_numeric($params['many'])) {
					$params['many'] = abs(intval($params['many']));
				} else {
					$params['many'] = true;
				}

				if (empty($params['type']) || !in_array($params['type'], array('file', 'image'))) {
					$params['type'] = 'file';
				}

				if ($params['many'] === 1) {
					$params['sort'] = false;
				} else {
					$params['sort'] = !empty($params['sort']);
				}

				if (!isset($params['preview'])) {
					if ($params['many'] !== 1 && $params['type'] === 'image') {
						$params['preview'] = true;
					} else {
						$params['preview'] = false;
					}
				}

				$params['isAttachment'] = !empty($params['isAttachment']);

				if (!isset($params['rule'])) {
					$params['rule'] = $rule;
				} else {
					if (!isset($params['rule']['types'])) {
						$params['rule']['types'] = File::fileTypes();
					}

					if (!isset($params['rule']['maxSize'])) {
						$params['rule']['maxSize'] = File::fileMaxSize();
					}

					if (!isset($params['rule']['minSize'])) {
						$params['rule']['minSize'] = 1024;
					}

					$params['rule']['tooLarge'] = sprintf('{file}太大了，必须小于%s', Yii::app()->format->size($params['rule']['maxSize']));
					$params['rule']['tooSmall'] = sprintf('{file}太小了，必须大于%s', Yii::app()->format->size($params['rule']['minSize']));
				}

				if ($params['type'] === 'image') {
					$params['rule']['types'] = Yii::app()->option->get('_file_image_types', 'jpg,jpeg,gif,png');
				}

				if (!isset($params['attribute'])) {
					$params['attribute'] = $field . '_id';
				}
				$params['isSelf'] = $owner->hasAttribute($params['attribute']); //是否是模型自带字段（表中有这个字段）
				$fields[$field] = $params;
			}
			$cache[$className] = $fields;
		}
		return $cache[$className];
	}

	/**
	 * 解析ids
	 *
	 * @param $ids
	 * @return array
	 */
	public function parseIds($ids)
	{
		if ($ids && is_string($ids)) {
			$ids = preg_split('/\s*,\s*/', trim($ids), -1, PREG_SPLIT_NO_EMPTY);
		} elseif (!is_array($ids)) {
			$ids = array();
		}

		return array_unique(array_map('intval', $ids));
	}

	/**
	 * 获取对象文件ids缓存Key
	 *
	 * @param integer $id 对象id
	 * @param string $field 对象字段名
	 * @return string
	 */
	public function getFileIdsCacheKey($id, $field)
	{
		$owner = $this->getOwner();
		$className = get_class($owner);
		return "{$className}_{$id}_{$field}_filesIds";
	}

	/**
	 * 获取图片文件列表 根据模型中的image属性
	 *
	 * @return array
	 */
	public function getImageFiles()
	{
		return $this->getFilesByField('image');
	}

	/**
	 * 根据对象字段获取该字段下的文件列表
	 *
	 * @param string $field
	 * @throws CException
	 * @return array
	 */
	public function getFilesByField($field)
	{
		$fields = $this->getUploadFields();
		if (!isset($fields[$field])) {
			throw new CException($field . '字段不是文件字段');
		}

		if (!isset($this->_files[$field])) {
			$fileIds = $this->getFileIdsByField($field);
			$this->_files[$field] = File::findFromCache($fileIds);
		}

		return $this->_files[$field];
	}

	/**
	 * 获取附件列表 根据模型中的attachment属性
	 *
	 * @return array
	 */
	public function getAttachmentFiles()
	{
		return $this->getFilesByField('attachment');
	}

	/**
	 * 获取附件文件 根据模型中的attachment属性
	 *
	 * @return File|null
	 */
	public function getAttachmentFile()
	{
		if ($fileId = current($this->getFileIdsByField('attachment'))) {
			return File::findFromCache($fileId);
		}
	}

	/**
	 * 获取对象字段上传允许的文件类型
	 *
	 * @param string $field
	 */
	public function getFileTypesByField($field)
	{
		$fields = $this->getUploadFields();
		if (isset($fields[$field])) {
			return $fields[$field]['rule']['types'];
		}
	}

	/**
	 * 获取对象字段上传允许的最大文件大小
	 *
	 * @param string $field
	 */
	public function getFileMaxSizeByField($field)
	{
		$fields = $this->getUploadFields();
		if (isset($fields[$field])) {
			return Yii::app()->format->size($fields[$field]['rule']['maxSize']);
		}
	}

	/**
	 * 上传文件到对象字段中去
	 *
	 * @param string $field 字段名
	 * @throws CException
	 * @return boolean|File
	 */
	public function upload($field)
	{
		$fields = $this->getUploadFields();
		if (!isset($fields[$field])) {
			throw new CException($field . '字段不是文件字段');
		}

		$owner = $this->getOwner();

		$validator = CValidator::createValidator('file', $owner, $field, $fields[$field]['rule']);
		$validator->validate($owner, $field);

		if ($owner->hasErrors($field)) {
			return false;
		}

		if (!$owner->$field instanceof CUploadedFile) {
			$uploadedFile = CUploadedFile::getInstance($owner, $field);
		} else {
			$uploadedFile = $owner->$field;
		}

		if (isset($fields[$field]['location'])) {
			$location = $fields[$field]['location'];
		} else {
			$location = Yii::app()->option->get('file_upload_location', 'public://');
		}

		if (is_callable($location)) {
			$location = call_user_func($location);
		}

		$directory = File::localPath($location);
		if (!$directory || !File::prepareDirectory($directory)) {
			throw new CException('上传目录不能被创建');
		}

		$filename = uniqid() . '.' . strtolower($uploadedFile->getExtensionName());
		if (substr($location, -1) == '/') {
			$uri = $location . $filename;
		} else {
			$uri = $location . '/' . $filename;
		}

		$filePath = File::localPath($uri);
		if ($uploadedFile->saveAs($filePath)) {
			$data = array(
				'user_id' => Yii::app()->getUser()->getId(),
				'bundle' => get_class($owner),
				'filename' => $uploadedFile->getName(),
				'uri' => $uri,
				'filemime' => $uploadedFile->getType(),
				'filesize' => $uploadedFile->getSize(),
				'status' => $owner instanceof File ? 1 : 0,
			);

			$file = new File();
			$file->setAttributes($data, false);
			$resize = isset($fields[$field]['resize']) ? $fields[$field]['resize'] : true;

			if ($fields[$field]['isAttachment']) {
				$resize = false;
			}

			if (isset($fields[$field]['watermark']) && Yii::app()->hasComponent('image')) {
				$watermark = $fields[$field]['watermark'];
				/** @var YGDImage $image */
				$image = Yii::app()->image->watermark($filePath, $watermark['filePath'], isset($watermark['position']) ? $watermark['position'] : array());
				$image->save($filePath);
			}

			if ($resize !== false) {
				$defaultSizes = File::imageSizes();
				if (!is_array($resize)) {
					$resize = array(File::IMAGE_THUMBNAIL => $defaultSizes[File::IMAGE_THUMBNAIL]);
				} else {
					$resize += array(File::IMAGE_THUMBNAIL => $defaultSizes[File::IMAGE_THUMBNAIL]);
				}

				$file->attachBehavior('imageResize', array(
					'class' => 'YImageResizeBehavior',
					'sizes' => $resize,
				));
			} elseif ($file->isImage()) {
				$imageSize = getimagesize($filePath);
				$file->meta['sizes'][File::IMAGE_ORIGIN] = array(
					'width' => $imageSize[0],
					'height' => $imageSize[1],
					'uri' => $uri,
				);
			}

			if ($file->save(false)) {
				return $file;
			} else {
				unlink($filePath);
				return false;
			}
		}
		return false;
	}

	/**
	 * 对象查找之后
	 */
	public function afterFind($event)
	{
		$this->setOldFields();
	}

	/**
	 * 设置旧的文件ids
	 */
	public function setOldFields()
	{
		$fields = $this->getUploadFields();
		$owner = $this->getOwner();
		foreach ($fields as $field => $params) {
			if ($params['isSelf']) {
				$attribute = $params['attribute'];
				$this->_oldFileIds[$field] = $this->parseIds($owner->$attribute);
			}
		}
	}

	/**
	 * 验证之前
	 *
	 * @see CModelBehavior::beforeValidate()
	 */
	public function beforeValidate($event)
	{
		$owner = $this->getOwner();
		if ($owner instanceof File) {
			return;
		}

		$fields = $this->getUploadFields();
		foreach ($fields as $field => $params) {
			if (!isset($owner->$field)) {
				continue;
			}

			$value = $this->parseIds($owner->$field);

			if ($params['many'] === 1) {
				$value = $value ? $value[0] : 0;
			} elseif ($params['many'] !== true) {
				$value = array_slice($value, 0, $params['many']);
			}

			$types = explode(',', $params['rule']['types']);
			$validFileIds = array();
			foreach (File::findFromCache((array) $value) as $model) {
				if (in_array($model->getExt(), $types) && $model->bundle == get_class($owner)) {
					$validFileIds[] = $model->id;
				}
			}

			if ($params['many'] === 1) {
				$validFileIds = $validFileIds ? $validFileIds[0] : 0;
			}

			$owner->$field = $validFileIds;
		}
	}

	/**
	 * 对象保存之前
	 *
	 * @see CActiveRecordBehavior::beforeSave()
	 */
	public function beforeSave($event)
	{
		$owner = $this->getOwner();
		if ($owner instanceof File) {
			return;
		}

		$fields = $this->getUploadFields();
		foreach ($fields as $field => $params) {
			if (!isset($owner->$field)) {
				continue;
			}

			if ($params['isSelf']) {
				$attribute = $params['attribute'];
				$value = $owner->$field;

				if ($value) {
					$owner->$attribute = is_array($value) ? implode(',', $value) : $value;
				} else {
					$owner->$attribute = 0;
				}
			}
		}
	}

	/**
	 * 对象保存之后
	 *
	 * @param CModelEvent $event
	 * @throws CDbException
	 * @throws Exception
	 */
	public function afterSave($event)
	{
		$owner = $this->getOwner();

		//File不保存
		if ($owner instanceof File) {
			return;
		}

		$fields = $this->getUploadFields();
		foreach ($fields as $field => $params) {
			if (!isset($owner->$field)) {
				continue;
			}

			$newFileIds = (array)$owner->$field;

			$oldFileIds = $delFileIds = $addFileIds = array();
			if ($owner->getIsNewRecord()) {
				$addFileIds = $newFileIds;
			} else {
				if ($params['isSelf']) {
					$oldFileIds = $this->_oldFileIds[$field];
				} else {
					$oldFileIds = $this->getFileIdsByField($field, true);
				}
				$delFileIds = array_diff($oldFileIds, $newFileIds);
				$addFileIds = array_diff($newFileIds, $oldFileIds);
			}

			$connection = $owner->getDbConnection();
			if (!$transaction = $connection->getCurrentTransaction()) {
				$transaction = $connection->beginTransaction();
			}

			$objectId = $owner->getPrimaryKey();
			try {
				if ($delFileIds) {
					$connection->createCommand()->delete('{{file_usage}}', array('and',
						'object_id=:object_id AND bundle=:bundle AND field=:field',
						array('in', 'file_id', $delFileIds),
					), array(':object_id' => $owner->getPrimaryKey(), ':bundle' => get_class($owner), ':field' => $field));

					foreach (File::model()->findAllByPk($delFileIds) as $file) {
						$file->status = File::STATUS_TEMPORARY;
						$file->delete();
					}
				}

				if ($addFileIds) {
					$qField = $connection->quoteValue($field);
					$qBundle = $connection->quoteValue(get_class($owner));
					$values = array();

					foreach ($addFileIds as $id) {
						$values[] = "($objectId, $id, $qBundle, $qField)";
					}
					$command = $connection->createCommand("INSERT INTO {{file_usage}} (object_id, file_id, bundle, field) VALUES " . implode(',', $values));
					$command->execute();
					File::model()->updateByPk($addFileIds, array('status' => File::STATUS_PERMANENT));
				}

				if ($params['sort'] && $newFileIds !== $oldFileIds) {
					foreach ($newFileIds as $i => $id) {
						$connection->createCommand()->update('{{file_usage}}', array('weight' => $i), 'file_id=:file_id', array(
							':file_id' => $id,
						));
					}
				}
				$transaction->commit();
			} catch (CDbException $e) {
				Yii::log('保存对象文件使用信息失败。错误信息：' . $e->getMessage(), CLogger::LEVEL_ERROR);
				$transaction->rollBack();
				return;
			}

			$this->updatefileIdsCache($objectId, $field, $newFileIds);
		}
	}

	/**
	 * 更新对象文件ids缓存
	 *
	 * @param integer $id 对象id
	 * @param string $field 对象字段名
	 * @param array $fileIds 文件ids
	 */
	public function updateFileIdsCache($id, $field, $fileIds)
	{
		$cacheKey = $this->getfileIdsCacheKey($id, $field);
		Yii::app()->getCache()->set($cacheKey, $fileIds, Yii::app()->option->get('_file_usage_ids_expire', 2592000)); //30天
	}

	/**
	 * 对象删除之后
	 *
	 * @param CModelEvent $event
	 */
	public function afterDelete($event)
	{
		$owner = $this->getOwner();
		$fields = $this->getUploadFields();

		if ($owner instanceof File) {
			return;
		}

		$objectId = $owner->getPrimaryKey();
		$bundle = get_class($owner);
		$connection = $owner->getDbConnection();

		foreach ($fields as $field => $params) {
			$fileIds = $this->getFileIdsByField($field);
			$connection->createCommand()->delete('{{file_usage}}', array('and',
				'object_id=:object_id AND bundle=:bundle AND field=:field',
				array('in', 'file_id', $fileIds),
			), array(':object_id' => $objectId, ':bundle' => $bundle, ':field' => $field));

			foreach (File::model()->findAllByPk($fileIds) as $file) {
				$file->delete();
			}

			$this->deletefileIdsCache($objectId, $field);
		}
	}

	/**
	 * 删除对象文件ids缓存
	 *
	 * @param integer $id 对象id
	 * @param string $field 对象字段名
	 */
	public function deleteFileIdsCache($id, $field)
	{
		$cacheKey = $this->getFileIdsCacheKey($id, $field);
		Yii::app()->getCache()->delete($cacheKey);
	}

	/**
	 * 根据文件字段下载字数排行
	 *
	 * @param string $field
	 * @param integer $limit
	 * @return CActiveRecord
	 */
	public function byDownloadTop($field, $limit = null)
	{
		$owner = $this->getOwner();
		if ($owner->hasAttribute('download_count')) {
			$owner->getDbCriteria()->mergeWith(array(
				'order' => 't.download_count DESC'
			));
		} else {
			$owner->getDbCriteria()->mergeWith(array(
				'join' => ' INNER JOIN {{file_usage}} AS t2 ON t2.object_id = t.id',
				'condition' => 'bundle=:bundle AND field=:field',
				'limit' => $limit,
				'order' => 't2.download_count DESC',
				'params' => array(':bundle' => get_class($owner), ':field' => $field),
			));
		}

		return $owner;
	}
}
